﻿// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved
// Developer:  Michael Antonio
using System;
using System.Linq;
using System.Net;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.IO;
#if !WINDOWS_PHONE
using System.Json;
#endif
using System.Xml.Linq;
using System.Collections.Generic;

///
/// Rolled my own WebClient-like request framework.  
/// There just should be a way to say "Hey, I've got this URL, get me the data in the correct format."
/// 
/// This follows the asynchronous pattern as much as possible.  
///
namespace MichMan.Utilities.Requests
{
    public delegate void RunRequestCompletedEventHandler(object sender, RunRequestCompletedEventArgs e);
    
    /// <summary>
    /// Which HTTP method to use.  Default is GET or POST (if there is a payoad).
    /// </summary>
    public enum Method
    {
        Default, // Uses GET or POST based on the Payload state.
        GET,
        POST,
        PUT,
        DELETE,
        HEAD,
        OPTIONS,
        TRACE
    };

    /// <summary>
    /// Where the request is in the processing pipeline.  
    /// </summary>
    public enum RequestStatus
    {
        Pending = 0,    // Not started.
        Started,

        // Be careful changing the order of statuses, or adding new status. There is code that assumes that
        // requests with RequestStatus >= Succeeded are completed, and RequestStatus < Succeeded has not completed

        Succeeded,
        Error,
        Cancelled,
    }

    public enum CacheStatus
    {
        /// <summary>
        /// We have not done any caching operations with this request.
        /// </summary>
        None = 0,
        /// <summary>
        /// Request was put in the cache.
        /// </summary>
        Stored,
        /// <summary>
        /// Request was retrieved from the cache.
        /// </summary>
        Retrieved,
        /// <summary>
        /// Request was removed from the cache.
        /// </summary>
        Removed,
        /// <summary>
        /// Transitory state.  We tried retrieving it, but could not.
        /// </summary>
        NotInCache,
        /// <summary>
        /// Policy settings prevent caching of this request, 
        /// or something prevented the data from being cached when it should have been.
        /// </summary>
        NotCachable
    }

    /// <summary>
    /// As it says.
    /// </summary>
    public class RunRequestCompletedEventArgs : AsyncCompletedEventArgs
    {
        public RunRequestCompletedEventArgs(Uri uri, RequestContext context) :
            base(context.Request.Error, context.Request.RequestStatus == RequestStatus.Cancelled, context.UserState)
        {
            Uri = uri;
            Context = context;
        }

        public Uri Uri { get; private set; }
        public RequestContext Context { get; private set; }
    }

    public static class RequestContextExtensions
    {
        public static RequestContext Context(this IAsyncResult asynchronousResult)
        {
            return (RequestContext)asynchronousResult.AsyncState;
        }

        public static RequestContext Context(this AsyncOperation asyncOperation)
        {
            return (RequestContext)asyncOperation.UserSuppliedState;
        }
    }

    /// <summary>
    /// Holds the request and response data for a request, the AsynchronousOperation, etc.
    /// </summary>
    public class RequestContext
    {
        internal RequestContext(IRequest request)
        {
            Request = request;
        }

        public HttpWebRequest WebRequest { get; internal set; }
        public HttpWebResponse WebResponse { get; internal set; }
        public Stream RequestStream { get; internal set; }
        public Stream ResponeStream { get; internal set; }
        public IRequest Request { get; internal set; }
        public object UserState { get; internal set; }

        internal AsyncOperation AsyncOp { get; set; }
        internal bool RunRequestCompletedCalled { get; set; }
    }

    /// <summary>
    /// Interface for all requests.
    /// </summary>
    public interface IRequest
    {
        /// <summary>
        /// What URI to call
        /// </summary>
        Uri Uri { get; }

        /// <summary>
        /// Status of the request.  
        /// </summary>
        RequestStatus RequestStatus { get; }

        /// <summary>
        /// Gets or the cache status.
        /// </summary>
        /// <value>The cache status.</value>
        CacheStatus CacheStatus { get; }

        /// <summary>
        /// If there are any exceptions when making the Web call, they will be put here
        /// </summary>
        Exception Error { get; }

        /// <summary>
        /// Call this to start a request.
        /// </summary>
        void RunRequestAsync();

        /// <summary>
        /// Call this to start a request.
        /// </summary>
        void RunRequestAsync(object userState);

        /// <summary>
        /// Cancel the WebRequest.
        /// </summary>
        void CancelAsync();

        /// <summary>
        /// Returns true if the request is in a completed state.
        /// </summary>
        bool IsCompleted { get; }
        
        /// <summary>
        /// True if the class is busy processing a request.
        /// </summary>
        bool IsBusy { get; }

        /// <summary>
        /// False if this request supports progress notifications.
        /// </summary>
        bool IsIndeterminate { get; }

        /// <summary>
        /// Event is always fired even if the request failed.
        /// </summary>
        event RunRequestCompletedEventHandler RunRequestCompleted;

        /// <summary>
        /// Progress notifications.
        /// </summary>
        event RunRequestProgressChangedEventHandler RunRequestProgressChanged;
    }

    /// <summary>
    /// Generic interface for GET requests
    /// </summary>
    /// <typeparam name="TResp"></typeparam>
    public interface IRequest<TResp> : IRequest
    {
        TResp Response { get; }
    }

    /// <summary>
    /// Generic interface for POST/PUT requests.
    /// </summary>
    /// <typeparam name="TReq"></typeparam>
    /// <typeparam name="TResp"></typeparam>
    public interface IRequest<TReq, TResp> : IRequest<TResp>
    {
        TReq Body { get; set; }
    }

    /// <summary>
    /// This is the base request class.  All requests derive from this.
    /// 
    /// Like WebClient and BackgroundWorker, this class implements the .Net Event-based Asynchronous Pattern (Bing it!).
    /// 
    /// It follows the naming convention from BackgroundWorker.RunWorkerAsync, so it's BaseRequest.RunRequestAsync.
    /// </summary>
    public abstract class BaseRequest
    {
        /// <summary>
        /// Constructor.  Requests should be constructed on the thread you want callbacks on.
        /// </summary>
        public BaseRequest()
        {
            // Default is to always deserialize the response.
            DeserializeResponse = true;
            Headers = new Dictionary<string, string>();

            RequestContext context = new RequestContext((IRequest)this);
            context.AsyncOp = AsyncOperationManager.CreateOperation(context);
            Context = context;
        }


        /// <summary>
        /// Uri must be set before calling RunRequestAsync().
        /// </summary>
        public Uri Uri { get; set; }
        public string Payload { get; set; }
        public virtual event RunRequestCompletedEventHandler RunRequestCompleted;

        protected bool DeserializeResponse { get; set; }
        protected RequestContext Context { get; set; }

        private Exception _error = null;
        public Exception Error
        {
            get
            {
                return _error;
            }
            protected set
            {
                if (_error == null)
                {
                    _error = value;
                    RequestStatus = RequestStatus.Error;
                }
            }
        }

        private RequestStatus _requestStatus = RequestStatus.Pending;
        public RequestStatus RequestStatus
        {
            get
            {
                return _requestStatus;
            }

            set
            {
                if (_requestStatus > value)
                {
                    throw new InvalidOperationException("Can't set request status to something lower than what it is.");
                }

                if (_requestStatus == value)
                {
                    // No effect.
                    return;
                }

                _requestStatus = value;

                switch (_requestStatus)
                {
                    case RequestStatus.Succeeded:
                    case RequestStatus.Error:
                    case RequestStatus.Cancelled:
                        {
                            OnRunRequestCompletedInternal(Context);
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Gets or sets the cache status.
        /// </summary>
        /// <value>The cache status.</value>
        public CacheStatus CacheStatus { get; set; }

        protected virtual bool FulfillFromCache()
        {
            return CacheStatus == Requests.CacheStatus.Retrieved;
        }

        /// <summary>
        /// Decides if we need to do two async calls for WebRequest processing.
        /// Override if there is no body for your POST/PUT, or use NoRequestBody class as DataRequest TReq
        /// </summary>
        protected virtual bool MethodHasPayload
        {
            get
            {
                switch (Method)
                {
                    case Method.POST:
                    case Method.PUT:
                        return true;
                    default:
                        return false;
                }
            }
        }

        private Method _method;

        /// <summary>
        /// Returns the HTTP method being used.
        /// </summary>
        protected Method Method
        {
            get
            {
                if (_method == Method.Default)
                {
                    return Payload == null ? Method.GET : Method.POST;
                }
                return _method;
            }
            set
            {
                _method = value;
            }
        }

        public bool IsIndeterminate
        {
            get
            {
                if (this.Context.WebRequest == null)
                {
                    return true;
                }
                return this.Context.WebRequest.AllowReadStreamBuffering == true;
            }
        }

        /// <summary>
        /// True if the class is busy processing a request.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return RequestStatus == RequestStatus.Started;
            }
        }

        /// <summary>
        /// Returns true if the request is in a completed state.
        /// </summary>
        public bool IsCompleted
        {
            get
            {
                return RequestStatus >= RequestStatus.Succeeded;
            }
        }

        /// <summary>
        /// Support userState as per Event-based Asynchronous Pattern guidelines
        /// </summary>
        /// <param name="userState"></param>
        public void RunRequestAsync(object userState)
        {
            Context.UserState = userState;
            RunRequestAsync();
        }

        /// <summary>
        /// Note to implementors:  When overriding this method, allow subclasses to set the Uri/Payload
        /// by only setting them if they have not been set already.  
        /// </summary>
        public virtual void RunRequestAsync()
        {
            try
            {
                if (Uri == null)
                {
                    Uri = UriInferer.InferUri(this);
                }

                // Add any inferred headers to the headers collection.
                foreach (var kvp in UriInferer.InferHeaders(this))
                {
                    Headers[kvp.Key] = kvp.Value;
                }

                if (Uri == null)
                {
                    throw new InvalidOperationException("Cannot make a request without setting the Uri first.");
                }

                if (IsBusy)
                {
                    throw new NotSupportedException("Cannot make a request while this request is busy.");
                }
            }
            catch (Exception e)
            {
                Error = e;
                return;
            }

            if (FulfillFromCache())
            {
                return;
            }

            RunRequestAsyncInternal();
        }

        #region WebRequest_Request
        public Dictionary<string, string> Headers { get; private set; }

        private HttpWebRequest NewWebRequestInternal()
        {
            HttpWebRequest request = null;
            try
            {
                request = NewWebRequest();
                if (request == null)
                {
                    Error = new InvalidOperationException("NewWebRequest returned null.");
                }
            }
            catch (Exception e)
            {
                Error = e;
            }
            return request;
        }
        
        /// <summary>
        /// Allows the derived class to add custom headers or otherwise modify the request.
        /// Note:  WritePayload is also a good place to add custom headers.
        /// </summary>
        /// <returns></returns>
        protected virtual HttpWebRequest NewWebRequest()
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Uri);
            request.Method = Method.ToString();

            foreach (var kvp in Headers)
            {
                request.Headers[kvp.Key] = kvp.Value;
            }

            return request;
        }

        /// <summary>
        /// Override this if you have a binary payload, or if you need to add custom headers AFTER the payload is written.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="stream"></param>
        protected virtual void WritePayload(HttpWebRequest request, Stream stream)
        {
            if (!String.IsNullOrEmpty(Payload))
            {
                using (TextWriter writer = new StreamWriter(stream))
                {
                    writer.Write(Payload);
                }
            }
        }

        /// <summary>
        /// Only used for abort.
        /// </summary>
        private IAsyncResult AsyncResult { get; set; }
        private object _lock = new object();

        /// <summary>
        /// Cancel the WebRequest.
        /// </summary>
        public void CancelAsync()
        {
            IAsyncResult asyncResult = null;

            lock (_lock)
            {
                if (AsyncResult != null && !AsyncResult.IsCompleted)
                {
                    asyncResult = AsyncResult;
                    AsyncResult = null;
                }
            }

            if (asyncResult != null)
            {
                asyncResult.Context().WebRequest.Abort();
                RequestStatus = RequestStatus.Cancelled;
            }
        }

        /// <summary>
        /// Can be called by derived classes to re-start the request in the case of a recoverable error.
        /// </summary>
        protected void RunRequestAsyncInternal()
        {
            // Allow requests to be re-run.
            if (_requestStatus != RequestStatus.Pending)
            {
                _requestStatus = RequestStatus.Pending;
                _error = null;

                RequestContext oldContext = Context;
                Context = new RequestContext((IRequest)this);
                Context.AsyncOp = oldContext.AsyncOp;
                oldContext.AsyncOp = null;

                // This should only happen if the caller is re-running the request after it is complete, rather than the request recovering itself from an error.
                if (Context.AsyncOp == null)
                {
                    Context.AsyncOp = AsyncOperationManager.CreateOperation(Context);
                }

            }

            RequestStatus = RequestStatus.Started;

            HttpWebRequest request = NewWebRequestInternal();
            if (request == null)
            {
                return;
            }

            RequestContext context = Context;
            context.WebRequest = request;
            if (MethodHasPayload)
            {
                IAsyncResult asyncResult = request.BeginGetRequestStream(new AsyncCallback((ar) =>
                {
                    try
                    {
                        Stream stream = request.EndGetRequestStream(ar);
                        context.RequestStream = stream;
                        WritePayload(request, stream);

                        IAsyncResult asyncResult2 = request.BeginGetResponse(new AsyncCallback(AsyncResponseCallback), context);
                        lock (_lock)
                        {
                            AsyncResult = asyncResult2;
                        }

                    }
                    catch (Exception e)
                    {
                        Error = e;
                    }

                }), context);

                lock (_lock)
                {
                    AsyncResult = asyncResult;
                }
            }
            else
            {
                AsyncResult = request.BeginGetResponse(new AsyncCallback(AsyncResponseCallback), context);
            }
        }

        private void AsyncResponseCallback(IAsyncResult asynchronousResult)
        {
            lock (_lock)
            {
                // Can't abort any more...
                AsyncResult = null;
            }
            if (RequestStatus != Requests.RequestStatus.Error)
            {
                ProcessResult(asynchronousResult);
            }
        }

        #endregion WebRequest_Request

        #region ProcessResults
        #region Generic_ProcessResults

        /// <summary>
        /// Override this to read the result stream as text.
        /// </summary>
        /// <param name="reader"></param>
        protected virtual void ProcessResult(TextReader reader)
        {
        }

        /// <summary>
        /// Override this to read the result stream as binary.
        /// </summary>
        /// <param name="stream"></param>
        protected virtual void ProcessResult(Stream stream)
        {
            try
            {
                if (stream != null)
                {
                    ProcessResult(new StreamReader(stream));
                }
            }
            catch (Exception e)
            {
                Error = e;
            }
        }

        #endregion Generic_ProcessResults

        //
        // The rest of these are specific to WebRequest, and would rarely be overridden.
        // 

        #region WebRequest_ProcessResults

        /// <summary>
        /// You probably don't need to override this.
        /// If anything, you might want to override ProcessResult(RequestContext context, IAsyncResult asynchronousResult) instead.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="asynchronousResult"></param>
        protected virtual void ProcessResult(IAsyncResult asynchronousResult)
        {
            RequestContext context = asynchronousResult.Context();
            try
            {
                context.WebResponse = (HttpWebResponse)context.WebRequest.EndGetResponse(asynchronousResult);
                if (DeserializeResponse)
                {
                    context.ResponeStream = context.WebResponse.GetResponseStream();
                }
            }
            catch (WebException ex)
            {
                // The "finally" clause below will take care of the callback.
                _error = ex;
                _requestStatus = RequestStatus.Error;

                try
                {
                    context.WebResponse = (HttpWebResponse)ex.Response;
                    if (DeserializeResponse)
                    {
                        context.ResponeStream = context.WebResponse.GetResponseStream();
                        if (context.ResponeStream.Length == 0)
                        {
                            context.ResponeStream = null;
                        }
                    }
                }
                catch
                {
                    // We've already got an error, forget about this one.
                }
            }
            catch (Exception ex)
            {
                // The "finally" clause below will take care of the callback.
                _error = ex;
                _requestStatus = RequestStatus.Error;
            }
            finally
            {
                try
                {
                    if (context.ResponeStream != null)
                    {
                        ProcessResult(context.ResponeStream);
                    }
                }
                catch(Exception e)
                {
                    // The "finally" clause below will take care of the callback.
                    if (_error == null)
                    {
                        _error = e;
                        _requestStatus = RequestStatus.Error;
                    }
                }
                finally
                {
                    // Note that this.Context could have been replaced by a call to RunRequestAsync().
                    // So it's important to close the original WebResponse.
                    if (context.WebResponse != null)
                    {
                        context.WebResponse.Close();
                    }

                    if (!IsCompleted)
                    {
                        // If there's no error by this point, that's success.
                        RequestStatus = RequestStatus.Succeeded;
                    }

                    // Note:  this may have already been called as a consequence 
                    // of the ProcessResult or RequestStatus calls above.
                    OnRunRequestCompletedInternal(context);
                }
            }
        }

        /// <summary>
        /// Only call RequestComplete once per context.
        /// </summary>
        /// <param name="context"></param>
        private void OnRunRequestCompletedInternal(RequestContext context)
        {
            if (!context.RunRequestCompletedCalled)
            {
                context.RunRequestCompletedCalled = true;
                OnRunRequestCompleted(new RunRequestCompletedEventArgs(Uri, context));
            }
        }

        /// <summary>
        /// Posts a call to RequestComplete to the main thread.
        /// 
        /// Override if certain error situations would cause you to re-issue
        /// the request by calling RequestAsyncInternal() again.  
        /// </summary>
        /// <param name="context"></param>
        protected virtual void OnRunRequestCompleted(RunRequestCompletedEventArgs runRequestCompletedEventArgs)
        {
            if (runRequestCompletedEventArgs.Context.AsyncOp != null)
            {
                runRequestCompletedEventArgs.Context.AsyncOp.PostOperationCompleted(RunRequestCompletedCallback, runRequestCompletedEventArgs);
                runRequestCompletedEventArgs.Context.AsyncOp = null;
            }
        }

        /// <summary>
        /// Wrapper
        /// </summary>
        /// <param name="requestCompleteArgs"></param>
        private void RunRequestCompletedCallback(object runRequestCompletedEventArgs)
        {
            if (RunRequestCompleted != null)
            {
                RunRequestCompleted(this, (RunRequestCompletedEventArgs)runRequestCompletedEventArgs);
            }
        }

        #endregion WebRequest_ProcessResults

        #endregion ProcessResults
    }

    #region ProgressChanged
    
    public delegate void RunRequestProgressChangedEventHandler(object sender, RunRequestProgressChangedEventArgs e);

    public class RunRequestProgressChangedEventArgs : ProgressChangedEventArgs
    {
        public RunRequestProgressChangedEventArgs(long bytesReceived, long totalBytesToReceive, RequestContext context)
            : base(Percentage(bytesReceived, totalBytesToReceive), context.UserState)
        {
            BytesReceived = bytesReceived;
            TotalBytesToReceive = totalBytesToReceive;
            Context = context;
        }

        private static int Percentage(long bytesReceived, long totalBytesToReceive)
        {
            double ratio = (double)bytesReceived / (double)totalBytesToReceive;
            return (int)(Math.Ceiling(ratio * 100));
        }

        public RequestContext Context { get; private set; }

        public long BytesReceived { get; private set; }
        public long TotalBytesToReceive { get; private set; }
    }

    /// <summary>
    /// Add progress reporting to BaseRequest class.  Set AllowReadStreamBuffering to false to enable progress reporting.
    /// 
    /// Note that progress is only reported for the response
    /// portion of the request, not the request portion.
    /// </summary>
    public abstract class BaseRequestWithProgress : BaseRequest, IRequest
    {
        public BaseRequestWithProgress()
        {
            BufferSize = 10240;  // 10K chunks at least
            AllowReadStreamBuffering = true; // Default is to not allow progress reporting.
        }

        private void RunRequestProgressCallback(object requstProgressChangedEventArgs)
        {
            if (RunRequestProgressChanged != null)
            {
                RunRequestProgressChanged(this, (RunRequestProgressChangedEventArgs)requstProgressChangedEventArgs);
            }
        }

        public event RunRequestProgressChangedEventHandler RunRequestProgressChanged;

        protected virtual void OnRunRequestProgressChanged(RunRequestProgressChangedEventArgs runRequestProgressChangedEventArgs)
        {
            if (runRequestProgressChangedEventArgs.Context.AsyncOp != null)
            {
                runRequestProgressChangedEventArgs.Context.AsyncOp.Post(RunRequestProgressCallback, runRequestProgressChangedEventArgs);
            }
        }

        protected override HttpWebRequest NewWebRequest()
        {
            HttpWebRequest request = base.NewWebRequest();
            request.AllowReadStreamBuffering = AllowReadStreamBuffering;
            return request;
        }

        /// <summary>
        /// Set this to false to get progress notifications.
        /// </summary>
        public bool AllowReadStreamBuffering { get; set; }

        /// <summary>
        /// The size of the buffer.  Minimum is 4K, default is 10K.
        /// 
        /// Note that progress notifications are expensive, so setting this value low will slow down your request.
        /// </summary>
        public int BufferSize { get; set; }

        protected Stream PartialResultStream { get; set; }

        /// <summary>
        /// Buffers the incoming data to a memory stream and reports progress.
        /// 
        /// Override this if you want to display partial data to the end user.
        /// </summary>
        protected virtual void ProcessPartialResult(byte[] buffer, int length, int transferred, int expected)
        {
            if (PartialResultStream == null)
            {
                PartialResultStream = new MemoryStream();
            }

            try
            {
                PartialResultStream.Write(buffer, 0, length);
                PartialResultStream.Flush();
            }
            finally
            {
                if (transferred == expected)
                {
                    if (PartialResultStream != null)
                    {
                        PartialResultStream.Seek(0, SeekOrigin.Begin);
                        // Stream is now complete.  Process the result normally.
                        base.ProcessResult(PartialResultStream);
                    }
                    else
                    {
                        // No partial result stream.  They're doing their own thing.
                        // Just report success.
                        RequestStatus = Requests.RequestStatus.Succeeded;
                    }
                }
            }
        }

        int numProgress = 0;
        protected override void ProcessResult(Stream stream)
        {
            if (AllowReadStreamBuffering)
            {
                // Stream is complete.  Process the result normally.
                base.ProcessResult(stream);
                return;
            }

            BufferSize = Math.Max(BufferSize, 4092);

            try
            {
                int expected = ParseUtility.SafeParse<int>(Context.WebResponse.Headers[HttpRequestHeader.ContentLength]);

                if (expected == 0)
                {
                    throw new InvalidOperationException("Invalid HTTP response.  Server did not set Content-Length header");
                }

                int transferred = 0;
                int current = 0;

                byte[] data = new byte[BufferSize];

                while (true)
                {
                    int read = stream.Read(data, current, data.Length - current);
                    transferred += read;
                    current += read;

                    if (current == data.Length || transferred == expected)
                    {
                        // Process partial data on this thread.
                        ProcessPartialResult(data, current, transferred, expected);

                        current = 0;

                        // Notify main thread of progress infrequently.
                        if (transferred == expected || (++numProgress) % 10 == 0)
                        {
                            RunRequestProgressChangedEventArgs args = new RunRequestProgressChangedEventArgs(transferred, expected, Context);
                            OnRunRequestProgressChanged(args);
                        }

                        if (transferred == expected)
                        {
                            break;
                        }
                        // Debug.WriteLine("Downloaded " + total.ToString());
                    }
                }
            }
            catch (Exception e)
            {
                Error = e;
            }
        }
    }
    #endregion ProgressChanged

    /// <summary>
    /// Use this to indicate that there is no response expected from a POST or PUT operation
    /// </summary>
    public class NoResponseBody
    {
    }

    /// <summary>
    /// Use this to indicate that there is no request body expected for an operation.
    /// </summary>
    public class NoRequestBody
    {
    }

    /// <summary>
    /// This class is the workhorse for GET.  
    /// 
    /// Note that TResp can be a DataContract type, but it can also be other types.
    /// 
    /// For example, all these are valid:
    /// 
    /// SimpleDataRequest<string>
    /// SimpleDataRequest<XElement>
    /// SimpleDataRequest<JsonObject>
    /// SimpleDataRequest<NoResponseBody>
    /// 
    /// You can support other TResp types by overriding ProcessResult(Stream stream).
    /// </summary>
    /// <typeparam name="TResp">Can be a data contract type, JsonValue type or string.</typeparam>
    public class DataRequest<TResp> : BaseRequestWithProgress, IRequest<TResp>
        where TResp : class
    {
        public DataRequest()
        {
            if (typeof(TResp) == typeof(NoResponseBody))
            {
                DeserializeResponse = false;
            }
        }

        /// <summary>
        /// Uses the cache manager to attempt to satisfy the request from the cache.
        /// </summary>
        /// <returns></returns>
        protected override bool FulfillFromCache()
        {
            if (CacheStatus == CacheStatus.None)
            {
                object response = CacheManager.RetrieveFromCache<object>(GetType(), this.Uri.ToString());
                if (response != null)
                {
                    if (response is TResp)
                    {
                        CacheStatus = CacheStatus.Retrieved;
                        Response = (TResp)response;
                    }
                    else if (response is string)
                    {
                        CacheStatus = CacheStatus.Retrieved;
                        ProcessResult(new StringReader(response as string));
                    }
                }
                else
                {
                    CacheStatus = CacheStatus.NotInCache;
                }
            }
            return CacheStatus == CacheStatus.Retrieved;
        }

        /// <summary>
        /// After a request succeeds, this attempts to cache the data.
        /// </summary>
        /// <param name="representation"></param>
        /// <returns></returns>
        protected virtual bool AddToCache(object representation)
        {
            // TODO:  AddToCache on the main thread.
            if (CacheStatus == CacheStatus.None || CacheStatus == CacheStatus.NotInCache)
            {
                if (Uri != null)
                {
                    CacheManager.AddToCache(GetType(), this.Uri.ToString(), representation);
                    CacheStatus = CacheStatus.Stored;
                }
            }
            return CacheStatus == CacheStatus.Stored;
        }

        private TResp _response = null;
        public TResp Response
        {
            get
            {
                return _response;
            }
            protected set
            {
                _response = value;
                AddToCache(_response);
                if (!IsCompleted)
                {
                    RequestStatus = RequestStatus.Succeeded;
                }
            }
        }

        /// <summary>
        /// Override this if you don't want to treat the results stream as JSon or XML.
        /// </summary>
        /// <param name="reader"></param>
        protected override void ProcessResult(TextReader reader)
        {
            // If the request is cachable, cache the string representation.
            string representation = null;
            if (CacheManager.IsCacheable(GetType()) && (CacheStatus == CacheStatus.None || CacheStatus == CacheStatus.NotInCache))
            {
                representation = reader.ReadToEnd();
                AddToCache(representation);
                reader = new StringReader(representation);
            }

            // Valid to use SimpleDataRequest<string>
            if (typeof(TResp) == typeof(string))
            {
                Response = reader.ReadToEnd() as TResp;
                return;
            }
#if !WINDOWS_PHONE
            // Valid to use SimpleDataRequest<JsonValue>
            // Valid to use SimpleDataRequest<JsonObject>
            // Valid to use SimpleDataRequest<JsonArray>
            // Valid to use SimpleDataRequest<JsonPrimitive>
            if (typeof(JsonValue).IsAssignableFrom(typeof(TResp)))
            {
                string jsonString = reader.ReadToEnd();
                Response = JsonValue.Parse(jsonString) as TResp;
                return;
            }
#endif
            ProcessResult(CreateXmlReader(reader));
        }

        /// <summary>
        /// Override to customize the XmlReader.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        protected virtual XmlReader CreateXmlReader(TextReader reader)
        {
            return XmlReader.Create(reader);
        }

        /// <summary>
        /// True if the type is has the [DataContract attribute]
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        protected bool IsDataContractType(Type t)
        {
            return t.GetCustomAttributes(typeof(DataContractAttribute), true).Any();
        }

        /// <summary>
        /// Override this if you need custom XML reading.
        /// </summary>
        /// <param name="reader"></param>
        protected virtual void ProcessResult(XmlReader reader)
        {
            // Valid to use SimpleDataRequest<XDocument>
            if (typeof(TResp) == typeof(XDocument))
            {
                Response = XDocument.Load(reader) as TResp;
            }
            if (typeof(TResp) == typeof(XElement))
            {
                Response = XElement.Load(reader) as TResp;
            }
            else if (IsDataContractType(typeof(TResp)))
            {
                Response = ReadDataContractResponse(reader);
            }
            else
            {
                Error = new NotSupportedException(String.Format("Type {0} is not supported.  Override the appropriate ProcessResult method to add support for {0}", typeof(TResp).Name));
            }
        }

        private TResp ReadDataContractResponse(XmlReader xmlReader)
        {
            TResp response = null;

            try
            {
                DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(TResp));
                response = dataContractSerializer.ReadObject(xmlReader, true) as TResp;
                xmlReader.Close();
            }
            catch (Exception e)
            {
                Error = e;
                response = null;
            }
            return response;
        }
    }

    /// <summary>
    /// This class is the workhorse for POST and PUT.  
    /// It allows you to make pretty complicated requests easily.
    /// 
    /// Note that TReq and TResp can both be DataContract types, but they can also be other types.
    /// 
    /// For example, all these are valid:
    /// 
    /// DataRequest<string, string>
    /// DataRequest<string, XElement>
    /// DataRequest<JsonObject, XElement>
    /// DataRequest<JsonObject, JSonValue>
    /// DataRequest<JsonObject, NoResponseBody>
    /// 
    /// You can support other TReq types by overriding "string DataToString(TReq data)" 
    /// or "void WritePayload(HttpWebRequest request, Stream stream)".
    /// 
    /// You can support other TResp types by overriding ProcessResult(Stream stream).
    /// </summary>
    /// <typeparam name="TReq">Can be a data contract type, JsonValue type, XElement or string.</typeparam>
    /// <typeparam name="TResp">Can be a data contract type, JsonValue type or string.</typeparam>
    public class DataRequest<TReq, TResp> : DataRequest<TResp>, IRequest<TReq, TResp>
        where TReq : class
        where TResp : class
    {
        protected DataRequest()
        {
        }

        public DataRequest(TReq data)
        {
            Body = data;
        }

        public TReq Body { get; set; }

        protected override void WritePayload(HttpWebRequest request, Stream stream)
        {
            if (Payload == null)
            {
                Payload = this.DataToString(Body);
            }
            
            base.WritePayload(request, stream);
        }

        public override void RunRequestAsync()
        {
            if (Body != null)
            {
                Method = Requests.Method.POST;
            }

            base.RunRequestAsync();
        }

        protected virtual string DataToString(TReq data)
        {
            if (typeof(TReq) == typeof(NoRequestBody))
            {
                return null;
            }

            if (data == null)
            {
                return null;
            }

            if (IsDataContractType(typeof(TReq)))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(TReq));

                using (MemoryStream ms = new MemoryStream())
                {
                    serializer.WriteObject(ms, data);
                    byte[] blob = ms.ToArray();
                    return Encoding.UTF8.GetString(blob, 0, blob.Length);
                }
            }

            return data.ToString();
        }
    }
}
